package com.ruoyi.web.controller.tool;

import org.apache.commons.compress.archivers.ArchiveException;
import org.apache.commons.compress.archivers.ArchiveInputStream;
import org.apache.commons.compress.archivers.ArchiveStreamFactory;
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import org.apache.commons.compress.utils.IOUtils;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;

public class UniversalExtractor {

    /**
     * 解压文件（自动识别格式）
     * @param compressedFilePath 压缩文件路径
     * @param outputDirectory 输出目录（可选，如果为null则解压到压缩文件所在目录下的同名目录）
     * @return 解压后的目录路径
     * @throws IOException 文件操作异常
     * @throws ArchiveException 压缩格式异常
     */
    public static String extract(String compressedFilePath, String outputDirectory) throws IOException, ArchiveException {
        Path path = Paths.get(compressedFilePath);
        String fileName = path.getFileName().toString().toLowerCase();

        // 如果未指定输出目录，则在压缩文件所在目录下创建同名目录
        if (outputDirectory == null) {
            String baseName = fileName.replaceAll("\\.(zip|tar\\.gz|tgz|tar|gz)$", "");
            outputDirectory = path.getParent().resolve(baseName).toString();
        }

        // 确保输出目录存在
        Path outputPath = Paths.get(outputDirectory);
        if (!Files.exists(outputPath)) {
            Files.createDirectories(outputPath);
        }

        if (fileName.endsWith(".zip")) {
            extractZip(path, outputDirectory);
        } else if (fileName.endsWith(".tar.gz") || fileName.endsWith(".tgz")) {
            extractTarGz(path, outputDirectory);
        } else if (fileName.endsWith(".tar")) {
            extractTar(path, outputDirectory);
        } else if (fileName.endsWith(".gz") && !fileName.endsWith(".tar.gz")) {
            extractGzip(path, outputDirectory);
        } else {
            // 尝试自动检测格式
            extractAuto(path, outputDirectory);
        }

        return outputDirectory;
    }

    /**
     * 解压ZIP文件
     */
    private static void extractZip(Path zipFile, String outputDirectory) throws IOException {
        try (InputStream is = Files.newInputStream(zipFile);
             ArchiveInputStream ais = new ArchiveStreamFactory()
                     .createArchiveInputStream(ArchiveStreamFactory.ZIP, is)) {

            extractArchive(ais, outputDirectory);
        } catch (ArchiveException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 解压TAR.GZ文件
     */
    private static void extractTarGz(Path tarGzFile, String outputDirectory) throws IOException {
        try (InputStream is = Files.newInputStream(tarGzFile);
             InputStream gis = new GzipCompressorInputStream(is);
             ArchiveInputStream ais = new ArchiveStreamFactory()
                     .createArchiveInputStream(ArchiveStreamFactory.TAR, gis)) {

            extractArchive(ais, outputDirectory);
        } catch (ArchiveException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 解压TAR文件
     */
    private static void extractTar(Path tarFile, String outputDirectory) throws IOException {
        try (InputStream is = Files.newInputStream(tarFile);
             ArchiveInputStream ais = new ArchiveStreamFactory()
                     .createArchiveInputStream(ArchiveStreamFactory.TAR, is)) {

            extractArchive(ais, outputDirectory);
        } catch (ArchiveException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 解压GZIP文件（非TAR.GZ）
     */
    private static void extractGzip(Path gzipFile, String outputDirectory) throws IOException {
        Path outputPath = Paths.get(outputDirectory);
        if (!Files.exists(outputPath)) {
            Files.createDirectories(outputPath);
        }

        String fileName = gzipFile.getFileName().toString();
        // 移除.gz后缀
        String outputFileName = fileName.substring(0, fileName.length() - 3);
        Path outputFile = outputPath.resolve(outputFileName);

        try (InputStream is = Files.newInputStream(gzipFile);
             InputStream gis = new GzipCompressorInputStream(is)) {

            Files.copy(gis, outputFile, StandardCopyOption.REPLACE_EXISTING);
        }
    }

    /**
     * 自动检测格式并解压
     */
    private static void extractAuto(Path file, String outputDirectory) throws IOException, ArchiveException {
        try (InputStream is = Files.newInputStream(file);
             ArchiveInputStream ais = new ArchiveStreamFactory()
                     .createArchiveInputStream(is)) {

            extractArchive(ais, outputDirectory);
        }
    }

    /**
     * 通用解压方法（处理ArchiveInputStream）
     */
    private static void extractArchive(ArchiveInputStream ais, String outputDirectory) throws IOException {
        Path outputPath = Paths.get(outputDirectory);
        if (!Files.exists(outputPath)) {
            Files.createDirectories(outputPath);
        }

        org.apache.commons.compress.archivers.ArchiveEntry entry;
        while ((entry = ais.getNextEntry()) != null) {
            if (!ais.canReadEntryData(entry)) {
                continue;
            }

            Path entryPath = outputPath.resolve(entry.getName()).normalize();
            if (!entryPath.startsWith(outputPath)) {
                throw new IOException("恶意压缩包: 包含路径遍历攻击");
            }

            if (entry.isDirectory()) {
                Files.createDirectories(entryPath);
            } else {
                Path parent = entryPath.getParent();
                if (parent != null && !Files.exists(parent)) {
                    Files.createDirectories(parent);
                }

                try (OutputStream os = Files.newOutputStream(entryPath)) {
                    IOUtils.copy(ais, os);
                }
            }
        }
    }

    /**
     * 主方法 - 使用示例
     */
    public static void main(String[] args) {
        String compressedFile = "C:\\Users\\TT\\Downloads\\ruoyi (5).zip";
        try {
            System.out.println("正在解压 " + compressedFile);
            // 不指定输出目录，自动解压到同名目录
            String extractedPath = extract(compressedFile, null);
            System.out.println("解压完成! 文件已解压到: " + extractedPath);
        } catch (Exception e) {
            System.err.println("解压失败: " + e.getMessage());
            e.printStackTrace();
        }
    }
}
